/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-12
 * V4.0
 */
package com.jphenix.share.tools;

import com.jphenix.share.lang.SDate;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.ver.PhenixVer;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.*;

/**
 * jar打包/解包工具类
 * 
 * 其中：
 *      生成备份包的格式：  [时间信息_毫秒_后缀]/备份文件名
 *      这样可以从文件夹中直接拿出原名的文件。
 *      
 *      后缀说明：  D 该文件被删除  E 改文件被修改 U该文件被上传文件覆盖
 *      
 * 2018-06-11 修改了doJar方法中，处理目标文件名时，如果指定了目标文件名，仍然重新修改设置了目标文件名。
 * 2019-07-15 备份包扩展名由jar改为zip
 * 2019-08-09 修改了程序。压缩包中文件路径开头不能是/，否则windows自带的程序不认压缩包
 * 2019-10-30 新增方法：将文件内容放入jar包
 * 2020-08-14 新增方法，通过文件对象构造压缩包文件对象
 * 2021-09-12 增加了将文件内容直接放入压缩包的方法 
 * 
 * @author 刘虻
 * 2007-1-6上午11:17:33
 */
@ClassInfo({"2021-09-12 18:51","jar打包/解包工具类"})
public class JarTools {

	protected int bufferSize = 2048; //缓存字节
	protected ArrayList<String> jumpOutFileArrayList = null; //需要跳过不压缩或解压缩的文件路径序列
	
	/**
	 * 获取需要跳过不压缩或解压缩的文件路径序列
	 * @author 刘虻
	 * 2007-1-6下午03:06:36
	 * @return 需要跳过不压缩或解压缩的文件路径序列
	 */
	public ArrayList<String> getJumpOutFileArrayList() {
		if (jumpOutFileArrayList==null) {
			jumpOutFileArrayList = new ArrayList<String>();
			jumpOutFileArrayList.add("META-INF/MANIFEST.MF");
		}
		return jumpOutFileArrayList;
	}
	
	
	/**
	 * 将一个文件放入jar文件中
	 * @param jarFilePath jar文件路径（如果jar文件已存在，则往现有的jar文件中新增）
	 * @param inFilePath 需要放入jar包中的文件
	 * @param subPath 压缩包中的相对路径
	 * 2014年8月25日
	 * @author 马宝刚
	 */
	public static void addJar(String jarFilePath,String inFilePath,String subPath) {
		//构建类实例
		JarTools jt = new JarTools();
		
		Object jarObj = jt.createJarFile(jarFilePath);
		try {
			jt.addJarFileElement(jarObj,SFilesUtil.getFileInputStreamByUrl(inFilePath,null),subPath);
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			jt.closeJarFile(jarObj,jarFilePath);
		}
	}
	
	/**
	 * 将文件内容放入jar包
	 * @param jarFilePath jar文件路径（如果jar文件已存在，则往现有的jar文件中新增）
	 * @param content     文件内容
	 * @param subPath     压缩包中的相对路径
	 * 2019年10月30日
	 * @author MBG
	 */
	public static void addContentJar(String jarFilePath,String content,String subPath) {
		//构建类实例
		JarTools jt = new JarTools();
		
		Object jarObj = jt.createJarFile(jarFilePath);
		try {
			jt.addJarFileElement(jarObj,content,"UTF-8",subPath);
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			jt.closeJarFile(jarObj,jarFilePath);
		}
	}
	
	/**
	 * 将指定文件打包
	 * @param path                    指定文件全路径
	 * @param basePath                文件根路径，如果存在，压缩包中的文件路径就变成了相对路径
	 * @param jarFilePath             压缩包文件路径（包含压缩文件名。如果为空，则放在要压缩文件同级路径。）
	 * @param jumpExistFile           如果压缩包已经存在该文件，是否跳过该文件
	 * @param deleteSourceAfterOver   压缩后是否删除源文件
	 * 2016年8月18日
	 * @author MBG
	 */
	public static void buildJar(
			String path,String basePath,String jarFilePath,boolean jumpExistFile,boolean deleteSourceAfterOver) {
		//构建类实例
		JarTools jt = new JarTools();
		jt.doJarThread(path,basePath,jarFilePath,jumpExistFile,deleteSourceAfterOver);
	}
	
	/**
	 * 执行压缩的线程类
	 * @author 刘虻
	 * 2007-1-6上午11:29:15
	 */
	protected class DoJarClass extends Thread {
		
		protected String path = null; //源路径
		protected String basePath = null; //压缩根路径，压缩后的压缩包中不包含根路径
		protected String jarFilePath =  null; //压缩文件目标路径
		protected boolean isJumpExistFile = false; //是否是否覆盖原jar中的文件
		protected boolean isDeleteSourceAfterOver = false; //压缩成功后是否删除源

		/**
		 * 构造函数
		 * 2007-1-6上午11:29:39
		 */
		public DoJarClass(
				String path
				,String basePath
				,String jarFilePath
				,boolean isJumpExistFile
				,boolean isDeleteSourceAfterOver) {
			super();
			setName("JarTools-DoJarClass");
			this.path = path;
			this.basePath = basePath;
			this.jarFilePath = jarFilePath;
			this.isJumpExistFile = isJumpExistFile;
			this.isDeleteSourceAfterOver = isDeleteSourceAfterOver;
		}
		
		
		/** 
		 * 覆盖方法
		 * @author 刘虻
		 * 2007-1-6上午11:32:04
		 */
		@Override
        public void run() {
			//执行压缩
			doJar(
					path
					,basePath
					,jarFilePath
					,isJumpExistFile
					,isDeleteSourceAfterOver);
		}
	}
	
	
	/**
	 * 执行解压缩线程类
	 * @author 刘虻
	 * 2007-1-6上午11:33:04
	 */
	protected class UndoJarClass extends Thread {
		
		private String basePath = null; //解压缩目标根路径
		private String jarFilePath = null; //jar文件路径
		private String backupBasePath = null; //备份根路径
		private boolean isJumpExistFile = false; //是否跳过已存在的文件
		private boolean isDeleteSourceAfterOver = false; //压缩成功后是否删除源
		private boolean nextPath = false; //是否直接解压二级文件夹
		
		/**
		 * 构造函数
		 * 2007-1-6上午11:35:13
		 */
		public UndoJarClass(
				String basePath
				,String jarFilePath
				,boolean isJumpExistFile
				,boolean isDeleteSourceAfterOver
				,boolean nextPath
				,String backupBasePath) {
			super();
			setName("JarTools-UndoJarClass");
			this.basePath = basePath;
			this.jarFilePath = jarFilePath;
			this.isJumpExistFile = isJumpExistFile;
			this.isDeleteSourceAfterOver = isDeleteSourceAfterOver;
			this.nextPath = nextPath;
			this.backupBasePath = backupBasePath;
		}
		
		
		/**
		 * 覆盖方法
		 * @author 刘虻
		 * 2007-1-6上午11:54:19
		 */
		@Override
        public void run() {
			
			//执行解压缩
			undoJar(
					basePath
					,jarFilePath
					,isJumpExistFile
					,isDeleteSourceAfterOver
					,nextPath
					,backupBasePath);
		}
	}
	
	
	
	
	/**
	 * 构造函数
	 * 2007-1-6上午11:17:33
	 */
	public JarTools() {
		super();
	}

	
	
	/**
	 * 在线程中压缩成jar包
	 * @author 刘虻
	 * 2007-1-6上午11:24:27
	 * @param path 源文件路径
	 * @param basePath 压缩根路径，压缩后的文件中不包含根路径
	 * @param jarFilePath jar文件保存路径（注意：该值需要是全路径）
	 * @param isJumpExistFile 是否是否覆盖原jar中的文件
	 * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
	 */
	public void doJarThread(
			String path
			,String basePath
			,String jarFilePath
			,boolean isJumpExistFile
			,boolean isDeleteSourceAfterOver) {
		//启动线程
		(new DoJarClass(
				path
				,basePath
				,jarFilePath
				,isJumpExistFile
				,isDeleteSourceAfterOver)).start();
	}

	   /**
     * 在线程中解压缩jar包
     * @author 刘虻
     * 2007-1-6上午11:25:08
     * @param basePath 解压缩目标根路径
     * @param jarFilePath jar包路径
     * @param isJumpExistFile 是否跳过存在的文件
     * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
     * @param nextPath 是否直接解压二级文件夹
     */
    public void undoJarThread(
            String basePath
            ,String jarFilePath
            ,boolean isJumpExistFile
            ,boolean isDeleteSourceAfterOver
            ,boolean nextPath) {
        undoJarThread(basePath,jarFilePath,isJumpExistFile,isDeleteSourceAfterOver,nextPath,null);
    }
	
	/**
	 * 在线程中解压缩jar包
	 * @author 刘虻
	 * 2007-1-6上午11:25:08
	 * @param basePath 解压缩目标根路径
	 * @param jarFilePath jar包路径
	 * @param isJumpExistFile 是否跳过存在的文件
	 * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
	 * @param nextPath 是否直接解压二级文件夹
	 * @param backupBasePath 备份根路径
	 */
	public void undoJarThread(
			String basePath
			,String jarFilePath
			,boolean isJumpExistFile
			,boolean isDeleteSourceAfterOver
			,boolean nextPath
			,String backupBasePath) {
		//启动线程
		(new UndoJarClass(
				basePath
				,jarFilePath
				,isJumpExistFile
				,isDeleteSourceAfterOver
				,nextPath
				,backupBasePath)).start();
	}
	
	
	
	/**
	 * 将jar文件中添加文件项
	 * @author 刘虻
	 * 2008-7-27下午04:46:13
	 * @param jarObject jar文件对象
	 * @param is 文件项输入流
	 * @param filePath 目标相对路径
	 */
	public void addJarFileElement(Object jarObject,InputStream is,String filePath) {
		if (jarObject==null 
				|| !(jarObject instanceof JarOutputStream)
				|| is==null
				|| filePath==null) {
			return;
		}
		byte[] buffer = new byte[10240]; //缓存
		try {
			//压缩包中的路径开头不能是/，否则windows自带的解压程序不认压缩包
			if(filePath.startsWith("/")) {
				filePath = filePath.substring(1);
			}
	        //构建压缩文件项
            JarEntry je = new JarEntry(filePath);
            je.setTime(System.currentTimeMillis()); //放入文件时间
            ((JarOutputStream)jarObject).putNextEntry(je);
            
			int count = is.read(buffer); //执行读取
			while(count>0) {
				((JarOutputStream)jarObject).write(buffer,0,count); //写入文件
				count = is.read(buffer); //执行读取
			}
			((JarOutputStream)jarObject).flush(); //刷新缓存
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e) {}
		}
	}
	
	
	/**
	 * 将jar文件中添加文件项
	 * @author 刘虻
	 * 2008-7-27下午04:46:13
	 * @param jarObject jar文件对象
	 * @param fileAllPath 文件全路径
	 * @param subPath 目标相对路径
	 */
	public void addJarFileElement(Object jarObject,String fileAllPath,String subPath) {
		InputStream is = null; //文件读入流
		try {
			is = new FileInputStream(new File(fileAllPath));
			//放入jar包
			addJarFileElement(jarObject,is,subPath);
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			is = null;
		}
	}
	
	/**
	 * 将指定内容添加到压缩包文件中
	 * @param jarObject  ar文件对象
	 * @param subPath    目标相对路径
	 * @param content    文件内容
	 * 2021年9月12日
	 * @author MBG
	 */
	public void addJarFileElement(Object jarObject,String subPath,byte[] content) {
		InputStream is = null; //文件读入流
		try {
			is = new ByteArrayInputStream(content);
			//放入jar包
			addJarFileElement(jarObject,is,subPath);
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				is.close();
			}catch(Exception e2) {}
			is = null;
		}
	}
	
	
	
	/**
	 * 将jar文件中添加文件项
	 * @author 刘虻
	 * 2008-7-27下午04:46:13
	 * @param jarObject jar文件对象
	 * @param content 文件内容
	 * @param filePath 目标相对路径
	 */
	public void addJarFileElement(Object jarObject,String content,String enc,String filePath) {
		if (jarObject==null 
				|| !(jarObject instanceof JarOutputStream)
				|| filePath==null) {
			return;
		}
		if(content==null) {
			content = "";
		}
		//压缩包中的路径开头不能是/，否则windows自带的解压程序不认压缩包
		if(filePath.startsWith("/")) {
			filePath = filePath.substring(1);
		}
		try {
	          //构建压缩文件项
            JarEntry je = new JarEntry(filePath);
            je.setTime(System.currentTimeMillis()); //放入文件时间
            ((JarOutputStream)jarObject).putNextEntry(je);
            if(enc==null || enc.length()<1) {
            	((JarOutputStream)jarObject).write(content.getBytes()); //写入文件
            }else {
            	((JarOutputStream)jarObject).write(content.getBytes(enc)); //写入文件
            }
			((JarOutputStream)jarObject).flush(); //刷新缓存
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	
	/**
	 * 写入Manifest文件
	 * @param jarObject   文件对象
	 * @param contentMap  内容容器
	 * 
	 * 注意：如果是 Class-Path 值，路径之间的分隔符是空格，不是冒号也不是分号
	 * 
	 * 2017年1月28日
	 * @author MBG
	 */
	public void addManifest(Object jarObject,Map<String,String> contentMap) {
		if(contentMap==null) {
			contentMap = new HashMap<String,String>();
		}
		//目标路径
		String subPath = "META-INF/MANIFEST.MF";
		//目标内容
		StringBuffer out = new StringBuffer();
		
		//获取主键序列
		List<String> keyList = BaseUtil.getMapKeyList(contentMap);
		
		String key = "Manifest-Version"; //提取主键
		if(keyList.contains(key)) {
			keyList.remove(key);
			out.append(key).append(": ").append(contentMap.get(key)).append("\r\n");
		}else {
			out.append(key).append(": 1.0").append("\r\n");
		}
		//框架版本号
		out.append("Version: JPhenix ").append(PhenixVer.getVER()).append(" ").append(PhenixVer.getUpdateCount()).append("\r\n");
	
		//默认运行类
		key = "Main-Class";
		if(keyList.contains(key)) {
			keyList.remove(key);
			out.append(key).append(": ").append(contentMap.get(key)).append("\r\n");
		}
		
		key = "class-version";
		keyList.remove(key);
		out.append(key).append(": ").append(System.getProperty("java.class.version")).append("\r\n");
		
		
		//编译环境
		key = "Created-By";
		keyList.remove(key);
		out
			.append(key)
			.append(": ")
			.append(System.getProperty("java.runtime.version"))
			.append("(").append(System.getProperty("java.vendor")).append(")").append("\r\n");
	
		key = "X-Compile-Source-JDK";
		keyList.remove(key);
		out.append(key).append(": ").append(System.getProperty("java.version")).append("\r\n");
		
		key = "X-Compile-Target-JDK";
		keyList.remove(key);
		out.append(key).append(": ").append(System.getProperty("java.version")).append("\r\n");
		
		//框架名
		key = "Name";
		keyList.remove(key);
		out.append(key).append(": JPhenix(http://www.jphenix.org)\r\n");
		
		key = "Implementation-Vendor";
		keyList.remove(key);
		out.append(key).append(": ChenXinSoft inc. Software Foundation(http://www.chenxinsoft.com)\r\n");
		
		for(String ele:keyList) {
			out.append(ele).append(": ").append(contentMap.get(ele)).append("\r\n");
		}
		
		//放入jar包中
		addJarFileElement(jarObject,out.toString(),"UTF-8",subPath);
	}
	
	
	
	/**
	 * 关闭Jar对象
	 * @author 刘虻
	 * 2008-7-27下午04:27:41
	 * @param jarObject jar对象
	 * @param filePath 文件路径
	 */
	public void closeJarFile(Object jarObject,String filePath) {
		if (jarObject==null 
				|| !(jarObject instanceof JarOutputStream)) {
			return;
		}
		try {
			((JarOutputStream)jarObject).closeEntry();
		}catch(Exception e) {}
		try {
			((JarOutputStream)jarObject).close();
		}catch(Exception e) {}
		if(filePath!=null) {
			//原jar文件
			File jarFile = new File(filePath);
			//jar临时文件
			File tempFile = new File(SFilesUtil.getFilePath(filePath,false)+SFilesUtil.getFileName(filePath)+".tmp");
			if(!tempFile.exists()) {
				return;
			}
			jarFile.delete();
			tempFile.renameTo(jarFile);
		}
	}
	
	/**
	 * 创建压缩包对象
	 * @param jarFile  压缩包文件对象
	 * @return         压缩包对象
	 * 2020年8月14日
	 * @author MBG
	 */
	public Object createJarFile(File jarFile) {
		return createJarFile(jarFile,null);
	}
	
	/**
	 * 创建压缩包对象
	 * @param jarFile  压缩包文件对象
	 * @param filePath 压缩包文件路径
	 * @return         压缩包对象
	 * 2020年8月14日
	 * @author MBG
	 */
	public Object createJarFile(File jarFile,String filePath) {
		if(jarFile==null) {
			return null;
		}
		if(filePath==null || filePath.length()<1) {
			filePath = jarFile.getPath();
		}
		//获取jar文件
		boolean append = false; //是否往已存在的jar文件中新增文件
		String tempPath = null; //临时文件路径
		if(jarFile.exists()) {
			//已存在压缩文件
			tempPath = SFilesUtil.getFilePath(filePath,false)+SFilesUtil.getFileName(filePath)+".tmp";
			jarFile = new File(tempPath);
			if(jarFile.exists()) {
				jarFile.delete();
			}
			append = true;
		}
		//构造文件输出流
		FileOutputStream fout = null;
		try {
			fout = new FileOutputStream(SFilesUtil.createFile(jarFile.getPath()),true);
		}catch(Exception e) {
			System.out.println(
					"create FileOutputStream error,path:"+filePath);
			e.printStackTrace();
			return null;
		}
		//构造jar文件输出流
		JarOutputStream jout = null;
		try {
			jout = new JarOutputStream(fout);
		}catch(Exception e) {
			System.out.println(
					"create JarOutputStream error,path:"+filePath);
			e.printStackTrace();
			try {
				fout.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			return null;
		}
		//往临时文件中新增已有的文件
		if(append) {
			//构造jar文件输入流
			JarInputStream jis = getJarInputStream(filePath);
			//迭代元素
			JarEntry entry = null;
			try {
				while((entry=jis.getNextJarEntry())!= null) {
					//输出压缩文件内容
					jout.putNextEntry(entry);
					
					//读取原jar文件中的数据
					int readByteCount = 0; //构造读取字节数
					byte[] buffer = new byte[bufferSize]; //构造缓存
					while ((readByteCount = jis.read(buffer)) != -1) {
						jout.write(buffer, 0, readByteCount);
					} 
				}
			}catch(Exception e) {
				System.out.println("out file in jar error,path:["+tempPath+"]");
				e.printStackTrace();
			}finally {
				try {
					jis.close();
				}catch(Exception e) {
					e.printStackTrace();
				}
				jis = null;
			}
		}
		return jout;
	}
	
	/**
	 * 建立jar文件
	 * @author 刘虻
	 * 2008-7-27下午04:20:34
	 * @param filePath 文件路径
	 * @return jar文件对象
	 */
	public Object createJarFile(String filePath) {
		return createJarFile(new File(filePath));
	}
	
	
	/**
	 * 压缩成jar包
	 * @author 刘虻
	 * 2007-1-6上午11:24:27
	 * @param path 源文件路径
	 * @param basePath 压缩根路径，压缩后的压缩包中不包含根路径
	 * @param jarFilePath jar文件保存路径 （注意：该值需要是全路径）
	 * @param isJumpExistFile 是否是否覆盖原jar中的文件
	 * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
	 */
	public void doJar(
			String path
			,String basePath
			,String jarFilePath
			,boolean isJumpExistFile
			,boolean isDeleteSourceAfterOver) {
		//导入参数合法化
		if (path==null || path.length()==0) {
			return;
		}
		File srcPathFile = new File(path);
		if (!srcPathFile.exists()) {
			return; 
		}
		//是否为现有jar包中新增文件
		boolean isAppend = false; 
		//处理根路径
		if (basePath!=null) {
			//basePath = basePath.toLowerCase(); //转为小写 （不能转换为小写，linux下的路径区分大小写）
			basePath = BaseUtil.swapString(basePath,"\\","/");
			if (!basePath.endsWith("/")) {
				basePath += "/";
			}
		}
		if (jarFilePath==null 
				|| jarFilePath.length()<1) {
			//获取最后一个路径名或文件名
			jarFilePath =  SFilesUtil.getFilePath(path,false)+"/"+SFilesUtil.getPathLastName(path)+".jar";
		}
		//保存文件路径
		ArrayList<String> filePathTmpArrl = new ArrayList<String>();
		if (srcPathFile.isFile()) {
			//如果是路径
			filePathTmpArrl.add(path);
		}else {
			try {
				SFilesUtil.getFileList(filePathTmpArrl,path,null,null,true,false);
			}catch(Exception e) {
				System.out.println("search file error,path:"+path);
				e.printStackTrace();
				return;
			}
		}
		//将所有获取的文件名路径都转换成小写（不能转换为小写）
		//注意：不能讲路径转换为小写，因为Linux下的路径是区分大小写的
		ArrayList<String> filePathArrl = new ArrayList<String>();
		//比较是否存在路径容器，该容器中保存的是去掉根路径的路径
		ArrayList<String> containsArrl = new ArrayList<String>();
		for (String filePath:filePathTmpArrl) {
			if (filePath==null || filePath.trim().length()<1) {
				continue;
			}
			//filePath = filePath.toLowerCase(); //转换为小写（不能转换为小写，linux下的路径区分大小写）
			filePath = BaseUtil.swapString(filePath,"\\","/");
			if (basePath!=null 
					&& filePath.startsWith(basePath)) {
				containsArrl.add(
						filePath.substring(
								basePath.length()));
			}else {
				//放入空字符串，为保持和filePathArrl中的元素总数同步
				containsArrl.add("");
			}
			filePathArrl.add(filePath);
		}
		//构造jar文件对象
		File jarFile = SFilesUtil.createFile(jarFilePath);
		if(jarFile.exists()) {
		    isAppend = true;
		    jarFile = SFilesUtil.createFile(jarFilePath+".tmp");
		}
		//构造文件输出流
		FileOutputStream fout = null;
		try {
			fout = new FileOutputStream(jarFile,true);
		}catch(Exception e) {
			System.out.println(
					"create FileOutputStream error,path:"+jarFilePath);
			e.printStackTrace();
			return;
		}
		//构造文件输入流
		FileInputStream fin = null;
		try {
			fin = new FileInputStream(SFilesUtil.createFile(jarFilePath));
		}catch(Exception e) {
			System.out.println(
					"create FileInputStream error,path:"+jarFilePath);
			e.printStackTrace();
			try {
				fout.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			return;
		}
		//构造jar文件输入流
		JarInputStream jin = null;
		try {
			jin = new JarInputStream(fin);
		}catch(Exception e) {
			System.out.println(
					"create JarInputStream error,path:"+jarFilePath);
			e.printStackTrace();
			try {
				fout.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			try {
				fin.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			return;
		}
		//构造文件列表
		Manifest manifest = jin.getManifest();
		if (manifest == null) {
			manifest = new Manifest();
		}
		//构造jar文件输出流
		JarOutputStream jout = null;
		try {
			jout = new JarOutputStream(fout,manifest);
		}catch(Exception e) {
			System.out.println(
					"create JarOutputStream error,path:"+jarFilePath);
			e.printStackTrace();
			try {
				fout.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			try {
				fin.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			try {
				jin.close();
			}catch(Exception ee) {
				ee.printStackTrace();
			}
			return;
		}
		//压缩时是否报错
		boolean hasError = false;
		//构造jar中已有文件路径序列
		ArrayList<String> oldJarFileArrl = new ArrayList<String>();
		if (!isJumpExistFile) {
			//如果要跳过jar中已存在的文件
			JarEntry entry = null;
			try {
				while((entry=jin.getNextJarEntry())!= null) {
					//放入已有文件路径
					String filePath = entry.getName();
					if (filePath==null 
							|| filePath.trim().length()==0
							|| getJumpOutFileArrayList().contains(filePath)) {
						continue;
					}
					oldJarFileArrl.add(filePath);
					//放入列表信息
					jout.putNextEntry(entry);
					//读取原jar文件中的数据
					int readByteCount = 0; //构造读取字节数
					byte[] buffer = new byte[bufferSize]; //构造缓存
					while ((readByteCount = jin.read(buffer)) != -1) {
						jout.write(buffer, 0, readByteCount);
					} 
					//关闭列表
					jout.closeEntry();
				}
			}catch(Exception e) {
				hasError = true;
				System.out.println(
						"get file path in jar error,path:"+jarFilePath);
				e.printStackTrace();
			}
		}
		//循环将要处理的文件
		for (int i=0;i<containsArrl.size();i++) {
			if (oldJarFileArrl.contains(containsArrl.get(i))) {
				//跳过已存在的文件
				continue;
			}
			try {
				//加入jar文件
				addFileToJar(jout, filePathArrl.get(i),basePath);
			}catch(Exception e) {
				hasError = true;
				e.printStackTrace();
			}
		}
		//关闭流
		try {
			jout.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		try {
			fout.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		try {
			jin.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		try {
			fin.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
		if(isAppend) {
		    //如果是往现有jar包
		      jarFile = SFilesUtil.createFile(jarFilePath);
		      if(jarFile.exists()) {
		          jarFile.delete();
		          SFilesUtil.createFile(jarFilePath+".tmp").renameTo(jarFile);
		        }
		}
		//压缩成功后，删除源
		if (!hasError && isDeleteSourceAfterOver) {
			try {
				SFilesUtil.deletePath(path);
			}catch(Exception e) {
				System.out.println("delete path error,path:"+path);
				e.printStackTrace();
			}
		}
	}
	
	
	/**
	 * 是否为jar文件
	 * @author 刘虻
	 * 2008-8-27下午11:12:52
	 * @param jarFile 文件对象
	 * @return true是
	 */
	public static boolean isJar(File jarFile) {
		try {
			(new JarFile(jarFile)).close();
			return true;
		}catch(Exception e) {}
		
		return false;
	}
	
	
	/**
	 * 获取压缩文件读入流
	 * @author 刘虻
	 * 2008-9-10下午12:47:39
	 * @param jarFilePath 压缩文件路径
	 * @return 压缩文件读入流
	 */
	public JarInputStream getJarInputStream(String jarFilePath) {
		//导入参数合法化
		if (jarFilePath==null 
				|| jarFilePath.trim().length()==0) {
			return null;
		}
		return getJarInputStream(new File(jarFilePath));
	}
	
	/**
	 * 获取压缩文件读入流
	 * @author 刘虻
	 * 2008-9-10下午12:13:00
	 * @param jarFile 压缩文件对象
	 * @return 压缩文件读入流
	 */
	public JarInputStream getJarInputStream(File jarFile) {
		if (jarFile==null || !jarFile.exists() || !jarFile.isFile()) {
			//没找到指定的jar文件
			return null;
		}
		//构建文件输入流
		FileInputStream fin = null;
		try {
			fin = new FileInputStream(jarFile);
		}catch(Exception e) {
			System.out.println(
					"create FileInputStream error,path:"+jarFile.getPath());
			e.printStackTrace();
			return null;
		}
		//构造jar文件输入流
		JarInputStream jin = null;
		try {
			jin = new JarInputStream(fin);
		}catch(Exception e) {
			System.out.println(
					"create JarInputStream error,path:"+jarFile.getPath());
			e.printStackTrace();
			try {
				fin.close();
			}catch(Exception e2) {
				e2.printStackTrace();
			}
		}
		return jin;
	}
	
	
	   /**
     * 输出指定文件
     * @author 刘虻
     * 2008-9-10下午12:15:29
     * @param basePath 输出根路径
     * @param jarEntry 压缩文件中指定文件的相对路径
     * @param jis 压缩文件读入流
     * @param isJumpExistFile 如果目标文件存在，是否跳过
     * @param nextPath 是否直接解压二级文件夹
     */
    public Map<String,String> outFileFromJar(
            String basePath
            ,JarEntry jarEntry
            ,JarInputStream jis
            ,boolean isJumpExistFile
            ,boolean nextPath) {
        return outFileFromJar(basePath,jarEntry,jis,isJumpExistFile,nextPath,null,null,null);
    }
	
	
	/**
	 * 输出指定文件
	 * @author 刘虻
	 * 2008-9-10下午12:15:29
	 * @param basePath 输出根路径
	 * @param jarEntry 压缩文件中指定文件的相对路径
	 * @param jis 压缩文件读入流
	 * @param isJumpExistFile 如果目标文件存在，是否跳过
	 * @param nextPath 是否直接解压二级文件夹
	 * @param backupBasePath 备份根路径
     * @param bakJout 完整备份包流对象
     * （前提是存在backupBasePath的值，在更新覆盖时，除了会建立每个独立文件
	 * @return 操作信息容器序列
	 * 
	 * key:
	 * 
	 *      path:操作文件路径
	 *      src_size: 源文件大小
	 *      obj_size: 覆盖文件大小
	 *      src_last_update: 源文件更新日期
	 *      obj_last_update: 目标文件更新日期
	 *      type: 0文件夹   1文件
	 *      mode: 0新增上传  1覆盖  2删除
	 *      status: 0操作失败  1成功
	 *      msg: 提示信息
     * 
	 */
	public Map<String,String> outFileFromJar(
			String basePath
			,JarEntry jarEntry
			,JarInputStream jis
			,boolean isJumpExistFile
			,boolean nextPath
			,String backupBasePath
			,JarOutputStream bakJout
			,List<String> srcFilePathList) {
		
		//构建返回值
		Map<String,String> resMap = new HashMap<String,String>();
		
		if (jarEntry==null) {
			resMap.put("status","0");
			resMap.put("msg","目标文件信息为空");
			return resMap;
		}
		String path = jarEntry.getName(); //压缩包中的路径
		if(nextPath) {
			int point = path.indexOf("/");
			if(point>-1) {
				path = path.substring(point+1);
			}
		}
		resMap = outFileFromJar(basePath,path,jis,isJumpExistFile,backupBasePath,bakJout,srcFilePathList);
		if(resMap!=null) {
			resMap.put("obj_size",String.valueOf(jarEntry.getSize()));
			resMap.put("obj_last_update",String.valueOf(jarEntry.getTime()));
		}
		return resMap;
	}
	
	/**
     * 输出指定文件
     * @author 刘虻
     * 2008-9-10下午12:15:29
     * @param basePath 输出根路径
     * @param jarFilePath 压缩文件中指定文件的相对路径
     * @param jis 压缩文件读入流
     * @param isJumpExistFile 如果目标文件存在，是否跳过
	 * @return 操作信息容器序列
	 * 
	 * key:
	 * 
	 *      path:操作文件路径
	 *      src_size: 源文件大小
	 *      obj_size: 覆盖文件大小
	 *      src_last_update: 源文件更新日期
	 *      obj_last_update: 目标文件更新日期
	 *      type: 0文件夹   1文件
	 *      mode: 0新增上传  1覆盖  2删除
	 *      status: 0操作失败  1成功
	 *      msg: 提示信息
     * 
     * 
     */
    public Map<String,String> outFileFromJar(
            String basePath
            ,String jarFilePath
            ,JarInputStream jis
            ,boolean isJumpExistFile) {
        return outFileFromJar(basePath,jarFilePath,jis,isJumpExistFile,null,null,null);
    }
    
    /**
     * 将指定文件添加到备份包中
     * @param srcFilePath            源文件根路径
     * @param backupBasePath    备份根路径（全路径）
     * 2015年7月23日
     * @author 马宝刚
     */
    public static void addBackupFile(String srcFilePath,String backupBasePath) {
        addBackupFile(srcFilePath,backupBasePath,null);
    }
    
    
    /**
     * 将指定文件添加到备份包中
     * @param srcFilePath            源文件根路径
     * @param backupBasePath     备份根路径（全路径）
     * @param suffix                       后缀（备份文件内的路径后缀）
     * 2015年7月23日
     * @author 马宝刚
     */
    public static void addBackupFile(String srcFilePath,String backupBasePath,String suffix) {
        //检测源文件是否合法
         File checkFile = new File(srcFilePath);
         if(!checkFile.exists() || checkFile.isDirectory()) {
             return;
         }
        //构建压缩处理类
        JarTools jt = new JarTools();
        //先备份源文件
        SDate now = new SDate(); //当前时间对象
        //执行备份
        String bakJarFilePath = SFilesUtil.fixPath(backupBasePath+"/"+srcFilePath+".zip");
        if(bakJarFilePath.length()>3 && bakJarFilePath.indexOf(":",3)>-1) {
            bakJarFilePath = bakJarFilePath.substring(0,3)+BaseUtil.swapString(bakJarFilePath.substring(3),":","_");
        }
        if(suffix==null) {
            suffix = "";
        }
        if(suffix.length()>0) {
            suffix = "_"+suffix;
        }
        //压缩包文件对象
        Object jarObj = jt.createJarFile(bakJarFilePath);
        try {
            jt.addJarFileElement(
                    jarObj,
                    SFilesUtil.getFileInputStreamByUrl(srcFilePath,null)
                    ,now.getDateNumberString()+"_"+now.getHourString()+now.getMinuteString()+now.getSecondString()+"_"+now.getMillisecondString()+suffix+"/"+SFilesUtil.getFileName(srcFilePath));
        }catch(Exception e) {
            e.printStackTrace();
        }finally {
            jt.closeJarFile(jarObj,bakJarFilePath);
        }
    }
    
	/**
	 * 输出指定文件
	 * @author 刘虻
	 * 2008-9-10下午12:15:29
	 * @param basePath 输出根路径
	 * @param jarFilePath 压缩文件中指定文件的相对路径
	 * @param jis 压缩文件读入流
	 * @param isJumpExistFile 如果目标文件存在，是否跳过
	 * @param backupBasePath 备份文件根路径
	 * @param bakJout 本次完整备份包流对象
	 * （前提是存在backupBasePath的值，在更新覆盖时，除了会建立每个独立文件
	 * 的增量备份包，也会为本次操作建立独立的备份包）
	 * @param srcFilePathList 目标文件夹中现存的文件路径序列
	 * 解压补丁包上传覆盖一个文件，就从路径序列中减去该路径
	 * 最后序列中剩下的文件路径，就是本次上传补丁中没有包含的文件
	 * 用来做完整上传补丁，即删除补丁中不存在而目标路径中存在的文件
	 * 
	 * @return 操作信息容器序列
	 * 
	 * key:
	 * 
	 *      path:操作文件路径
	 *      src_size: 源文件大小
	 *      obj_size: 覆盖文件大小
	 *      src_last_update: 源文件更新日期
	 *      obj_last_update: 目标文件更新日期
	 *      type: 0文件夹   1文件
	 *      mode: 0新增上传  1覆盖  2删除
	 *      status: 0操作失败  1成功
	 *      msg: 提示信息
	 * 
	 */
	private Map<String,String> outFileFromJar(
			String basePath
			,String jarFilePath
			,JarInputStream jis
			,boolean isJumpExistFile
			,String backupBasePath
			,JarOutputStream bakJout
			,List<String> srcFilePathList) {
		if (basePath==null 
				|| basePath.length()<1 
				|| jarFilePath==null 
				|| jarFilePath.length()<1
				|| getJumpOutFileArrayList().contains(jarFilePath)
				|| jis==null) {
			return new HashMap<String,String>();
		}
		if(jarFilePath.endsWith("/")) {
			//不针对文件夹操作
			return null;
		}
		//构建返回值
		Map<String,String> resMap = new HashMap<String,String>();
		resMap.put("path",jarFilePath);
		resMap.put("status","0");
    	
		if(jarFilePath.endsWith("._deleted_")) {
			
			resMap.put("mode","2");
			
		    //删除该文件
		    jarFilePath = jarFilePath.substring(0,jarFilePath.length()-10);
		    
		    //准备删除需要备份的文件序列
		    List<String> bakFileList = new ArrayList<String>();
		    boolean deleteFolder= false; //是否删除整个文件夹
		    if(jarFilePath.endsWith("__delete_folder__")) {
		        deleteFolder = true;
		        
		        resMap.put("type","0");
		        
		        //遇到标记删除整个文件夹
		        jarFilePath = SFilesUtil.getFilePath(jarFilePath,false);
		        try {
		            SFilesUtil.getFileList(bakFileList,jarFilePath,null,null,true,false);
		        }catch(Exception e) {
		            e.printStackTrace();
		        }
		    }else {
		    	
		    	resMap.put("type","1");
		    	
		        bakFileList.add(SFilesUtil.getAllFilePath(jarFilePath,basePath));
		    }
		    for(String filePath:bakFileList) {
		        //构造输出文件对象
	            File outFile = new File(filePath);
	            
	            if (!outFile.exists()) {
	            	resMap.put("msg","没找到目标");
	                return resMap;
	            }
	            if(srcFilePathList!=null) {
	                srcFilePathList.remove(fixFilePath(outFile));
	            }
	            if(!outFile.isDirectory() && backupBasePath!=null && backupBasePath.length()>0) {
	                //先备份源文件
	                SDate now = new SDate(); //当前时间对象
	                //执行备份
	                String bakJarFilePath = SFilesUtil.fixPath(backupBasePath+"/"+filePath+".jar");
	                if(bakJarFilePath.length()>3 && bakJarFilePath.indexOf(":",3)>-1) {
	                    bakJarFilePath = bakJarFilePath.substring(0,3)+BaseUtil.swapString(bakJarFilePath.substring(3),":","_");
	                }
	                
	                //放入完整备份包
	                if(bakJout!=null) {
	                    try {
	                        addFileToJar(bakJout,filePath,basePath);
	                    }catch(Exception e) {
	                        e.printStackTrace();
	                    }
	                }
	                
	                Object jarObj = createJarFile(bakJarFilePath); //构建独立增量备份包对象
	                try {
	                    //增加后缀U代表该备份文件被覆盖
	                    addJarFileElement(
	                            jarObj,
	                            SFilesUtil.getFileInputStreamByUrl(filePath,null)
	                            ,now.getDateNumberString()+"_"+now.getHourString()+now.getMinuteString()+now.getSecondString()+"_"+now.getMillisecondString()+"_D"+"/"+SFilesUtil.getFileName(filePath));
	                    if(!deleteFolder) {
	                        outFile.delete(); //删除文件
	                    }
	                }catch(Exception e) {
	                    e.printStackTrace();
	                }finally {
	                    closeJarFile(jarObj,bakJarFilePath);    //关闭独立文件增量备份包
	                }
	            }
		    }
		    if(deleteFolder) {
		        try {
		            SFilesUtil.deletePath(SFilesUtil.getAllFilePath(jarFilePath,basePath));
		        }catch(Exception e) {
		            e.printStackTrace();
		        }
		    }
		    resMap.put("status","1");
		    return resMap;
		}
		//构造输出流
		FileOutputStream fos = null;
        //拼装全路径
        jarFilePath = basePath + jarFilePath;
		try {
			//构造输出文件对象
			File outFile = new File(jarFilePath);
			
			if (outFile.exists()) {
				
				resMap.put("mode","1");
				resMap.put("src_size",String.valueOf(outFile.length()));
				resMap.put("src_last_update",String.valueOf(outFile.lastModified()));
				
			    if(isJumpExistFile) {
		             //跳过存在的文件
			    	resMap.put("msg","被设置为跳过不覆盖当前文件");
	                return resMap;
			    }
			    
			    if(srcFilePathList!=null) {
			        srcFilePathList.remove(fixFilePath(outFile));
			    }
	            
			    if(!outFile.isDirectory() && backupBasePath!=null && backupBasePath.length()>0) {
			        //先备份源文件
			        SDate now = new SDate(); //当前时间对象
			        //执行备份
			        String bakJarFilePath = SFilesUtil.fixPath(backupBasePath+"/"+jarFilePath+".jar");
			        if(bakJarFilePath.length()>3 && bakJarFilePath.indexOf(":",3)>-1) {
			            bakJarFilePath = bakJarFilePath.substring(0,3)+BaseUtil.swapString(bakJarFilePath.substring(3),":","_");
			        }
			        if(bakJout!=null) {
		                 //放入完整备份包
			            addFileToJar(bakJout,SFilesUtil.getAllFilePath(jarFilePath,basePath),basePath);
			        }
			        Object jarObj = createJarFile(bakJarFilePath);
			        try {
			            //增加后缀U代表该备份文件被覆盖
			            addJarFileElement(
			                    jarObj,
			                    SFilesUtil.getFileInputStreamByUrl(jarFilePath,null)
			                    ,now.getDateNumberString()+"_"+now.getHourString()+now.getMinuteString()+now.getSecondString()+"_"+now.getMillisecondString()+"_U"+"/"+SFilesUtil.getFileName(jarFilePath));
			        }catch(Exception e) {
			            e.printStackTrace();
			        }finally {
	                    closeJarFile(jarObj,bakJarFilePath);    //关闭独立文件增量备份包
			        }
			    }
			}else if(backupBasePath.length()>0){
			    //新上传的文件，目标文件夹中本来没有
			    //在压缩包中放一个标记该文件需要被删除的标记
			    //这样在出问题需要还原时，会删除这个原本目标路径中没有
			    //，但是原上传压缩包中存在的文件
				
				//标记为新增的文件
				resMap.put("mode","0");
			    
			    //检查当前文件夹在目标路径中是否存在，如果不存在，生成标记删除标志文件
			    buildDelFolder(jarFilePath,basePath,bakJout);
			    
                try {
                    //被删除的标志文件内容
                    ByteArrayInputStream bis = new ByteArrayInputStream("for deleted".getBytes());
                    //加入到完整备份包中
                    if(bakJout!=null) {
                        addJarFileElement(bakJout,bis,SFilesUtil.getSubPath(jarFilePath+"._deleted_",basePath));
                    }
                }catch(Exception e) {
                    e.printStackTrace();
                }
			}else {
				//标记为新增的文件
				resMap.put("mode","0");
			}
			
			//建立文件,如果没有指定的文件夹，连文件夹一起建立
			outFile = SFilesUtil.createFile(jarFilePath);
			if(outFile.isDirectory() || !outFile.exists()) {
			    //文件夹或者没生成这个文件，直接返回
				resMap.put("msg","目标是个文件夹，不能用文件覆盖文件夹，或者建立文件失败");
			    return resMap;
			}
			try {
			    //解压文件，覆盖源文件
				fos = new FileOutputStream(outFile);
			}catch(Exception e2) {
				System.out.println(
						"create FileOutputStream error,path:"+jarFilePath);
				e2.printStackTrace();
				resMap.put("msg","输出文件发生异常["+e2.getMessage()+"]");
				return resMap;
			}
			//读取原jar文件中的数据
			int readByteCount = 0; //构造读取字节数
			byte[] buffer = new byte[bufferSize]; //构造缓存
			while ((readByteCount = jis.read(buffer)) != -1) {
				fos.write(buffer, 0, readByteCount);
			} 
			resMap.put("status","1");
		}catch(Exception e) {
			System.out.println(
					"out file in jar error,path:"+jarFilePath);
			e.printStackTrace();
			resMap.put("msg","传输文件发生异常：["+e.getMessage()+"]");
		}finally {
			//关闭输出流
			try {
				fos.close();
			}catch(Exception e2) {}
		}
		return resMap;
	}
	
	
	/**
	 * 如果指定文件夹并不存在，则往jar包中
	 * 生成一条带删除该文件夹的标记文件
	 * 
	 * 听起来比较难懂，实际上这个功能只是
	 * 给上传补丁包时，生成还原补丁包的功能
	 * @param filePath 指定文件路径
	 * @param basePath 文件夹根路径
	 * @param jos 补丁包输出流
	 * 2015年12月8日
	 * @author 马宝刚
	 */
	private void buildDelFolder(String filePath,String basePath,JarOutputStream jos) {
	    if(jos==null) {
	        return;
	    }
	    filePath = SFilesUtil.getFilePath(filePath,false);
	    if((new File(filePath)).exists()) {
	        return;
	    }
	    //检查上一级文件夹是否存在
	    buildDelFolder(SFilesUtil.getAllFilePath(filePath+"/../",basePath),basePath,jos);
	    
        try {
            //被删除的标志文件内容
            ByteArrayInputStream bis = new ByteArrayInputStream("for deleted".getBytes());
            //加入到完整备份包中
            addJarFileElement(jos,bis,SFilesUtil.getSubPath(filePath+"/__delete_folder__._deleted_",basePath));
        }catch(Exception e) {
            e.printStackTrace();
        }
	}
	
	/**
     * 解压缩jar包
     * @author 刘虻
     * 2007-1-6上午11:25:08
     * @param basePath 解压缩目标根路径
     * @param jarFilePath jar包路径
     * @param isJumpExistFile 是否跳过存在的文件
     * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
     * @param nextPath 是否直接解压二级文件夹（有时打包会将有效的文件夹外再套一级文件夹）
     */
    public void undoJar(
            String basePath
            ,String jarFilePath
            ,boolean isJumpExistFile
            ,boolean isDeleteSourceAfterOver
            ,boolean nextPath) {
        undoJar(basePath,jarFilePath,isJumpExistFile,isDeleteSourceAfterOver,nextPath,null);
    }
    
    
    /**
     * 解压缩jar包
     * @author 刘虻
     * 2007-1-6上午11:25:08
     * @param basePath 解压缩目标根路径
     * @param jarFilePath jar包路径
     * @param isJumpExistFile 是否跳过存在的文件
     * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
     * @param nextPath 是否直接解压二级文件夹（有时打包会将有效的文件夹外再套一级文件夹）
     * @param backupBasePath 备份根路径（如果不为空，会将覆盖前的文件打包）
     */
    public void undoJar(
            String basePath
            ,String jarFilePath
            ,boolean isJumpExistFile
            ,boolean isDeleteSourceAfterOver
            ,boolean nextPath
            ,String backupBasePath) {
        undoJar(basePath,jarFilePath,isJumpExistFile,isDeleteSourceAfterOver,nextPath,false,backupBasePath);
    }
    
    
	/**
	 * 解压缩jar包
	 * @author 刘虻
	 * 2007-1-6上午11:25:08
	 * @param basePath 解压缩目标根路径（全路径）
	 * @param jarFilePath jar包路径
	 * @param isJumpExistFile 是否跳过存在的文件
	 * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
	 * @param nextPath 是否直接解压二级文件夹（有时打包会将有效的文件夹外再套一级文件夹）
	 * @param fullUpload 是否做完整上传（上传后，的文件与上传的包中的文件完全一致。如果原
	 *                                  本目标文件夹中的文件，没在上传包中，会删除该文件）说白了就跟先删
	 *                                  除目标文件夹，在做上传的原理是一样的
	 * @param backupBasePath 备份根路径（如果不为空，会将覆盖前的文件打包）
	 */
	public void undoJar(
			String basePath
			,String jarFilePath
			,boolean isJumpExistFile
			,boolean isDeleteSourceAfterOver
			,boolean nextPath
			,boolean fullUpload
			,String backupBasePath
			,Map<String,Map<String,String>> resInfoMap) {
	    
	    basePath = SFilesUtil.fixPath(basePath); //整理文件夹路径
	    
	    //当前目录中的全部文件信息序列（包含子文件夹）
	    //在做文件覆盖操作时，会从序列中删除该文件信息
	    //最后完成上传压缩包动作后，检查改序列中还存在哪些
	    //文件路径，这些文件就是压缩包中不包含的
	    List<String> srcFileList = new ArrayList<String>();

        //备份包文件名
        String backupPackageName = null;
        //构造jar文件输出流
        JarOutputStream jout = null;
        if(backupBasePath!=null && backupBasePath.length()>0) {
            backupPackageName =  (new SDate()).getDateNumberString()+"_";
            
            int fileNo = 0; //文件序号
            //构建返回值
            File resFile = new File(backupBasePath+"/covered/"+backupPackageName+(fileNo++)+".jar");
            while(resFile.exists()){
                resFile = new File(backupBasePath+"/covered/"+backupPackageName+(fileNo++)+".jar");
            }
            try {
                jout = new JarOutputStream(new FileOutputStream(SFilesUtil.createFile(resFile.getPath()),true),new Manifest());
            }catch(Exception e) {
                System.out.println(
                        "create JarOutputStream error,path:"+jarFilePath);
                e.printStackTrace();
                try {
                    jout.close();
                }catch(Exception ee) {}
                return;
            }
        }
	    
		//构造jar文件输入流
		JarInputStream jis = getJarInputStream(jarFilePath);
		//迭代元素
		JarEntry entry = null;
		boolean isFirst = true; //是否为第一次循环
		try {
			while((entry=jis.getNextJarEntry())!= null) {
			    if(isFirst) {
			        isFirst = false;
			        if(fullUpload) {
			            try {
			                if(nextPath) {
			                    //就在当前文件夹做解压
			                    SFilesUtil.getFileList(srcFileList,SFilesUtil.getFilePath(basePath,false),null,null,true,false);
			                }else {
			                    //在压缩包中第一级文件夹做解压
			                    String subPath = SFilesUtil.getFilePath(basePath+entry.getName(),false);
			                    if(subPath.endsWith("/")) {
			                        subPath = subPath.substring(0,subPath.length()-1);
			                    }
			                    SFilesUtil.getFileList(srcFileList,subPath,null,null,true,false);
			                }
			            }catch(Exception e) {
			                e.printStackTrace();
			            }
			        }
			    }
				//输出压缩文件内容
				Map<String,String> res = outFileFromJar(basePath,entry,jis,isJumpExistFile,nextPath,backupBasePath,jout,srcFileList);
				if(resInfoMap!=null) {
					resInfoMap.put(entry.getName(),res);
				}
			}
		}catch(Exception e) {
			System.out.println(
					"out file in jar error,path:"+jarFilePath);
			e.printStackTrace();
		}finally {
			try {
				jis.close();
			}catch(Exception e) {
				e.printStackTrace();
			}
			jis = null;
		}
		if(fullUpload && backupBasePath!=null && backupBasePath.length()>0) {
            SDate now = new SDate(); //当前时间对象
            String bakJarFilePath = null; //需要做独立文件增量备份的jar文件
		      //在上传完补丁后，检查目标文件夹中
	        //中剩余的文件，如果存在这些文件，
		    //将这些文件删除，因为这些文件不包含在补丁中
	        for(String path:srcFileList) {
	            //构造输出文件对象
	            File outFile = new File(path);
	            if (!outFile.exists()) {
	                continue;
	            }
	            if(!outFile.isDirectory()) {
	                //先备份源文件
	                //执行备份
	                bakJarFilePath = SFilesUtil.fixPath(backupBasePath+"/"+path+".jar");
	                if(bakJarFilePath.length()>3 && bakJarFilePath.indexOf(":",3)>-1) {
	                    bakJarFilePath = bakJarFilePath.substring(0,3)+BaseUtil.swapString(bakJarFilePath.substring(3),":","_");
	                }
	                
	                //放入完整备份包
	                doJar(path,basePath,backupPackageName,false,false);
	                
	                //构建独立增量备份包
	                Object jarObj = createJarFile(bakJarFilePath);
	                try {
	                    //增加后缀U代表该备份文件被覆盖
	                    addJarFileElement(
	                            jarObj,
	                            SFilesUtil.getFileInputStreamByUrl(jarFilePath,null)
	                            ,now.getDateNumberString()+"_"+now.getHourString()+now.getMinuteString()+now.getSecondString()+"_"+now.getMillisecondString()+"_D"+"/"+SFilesUtil.getFileName(jarFilePath));
	                    
	                    outFile.delete(); //删除文件
	                }catch(Exception e) {
	                    e.printStackTrace();
	                }finally {
	                    closeJarFile(jarObj,bakJarFilePath);    //关闭独立文件增量备份包
	                }
	            }
	        }
		}
		
		//关闭备份流
		try {
		    jout.close();
		}catch(Exception e) {}
		
		//解压缩成功后，删除源
		if (isDeleteSourceAfterOver) {
			try {
				SFilesUtil.deletePath(jarFilePath);
			}catch(Exception e) {
				System.out.println("delete path error,path:"+jarFilePath);
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 解压缩jar包
	 * @author 刘虻
	 * 2007-1-6上午11:25:08
	 * @param basePath 解压缩目标根路径（全路径）
	 * @param jarFilePath jar包路径
	 * @param isJumpExistFile 是否跳过存在的文件
	 * @param isDeleteSourceAfterOver 压缩成功后是否删除源 true是
	 * @param nextPath 是否直接解压二级文件夹（有时打包会将有效的文件夹外再套一级文件夹）
	 * @param fullUpload 是否做完整上传（上传后，的文件与上传的包中的文件完全一致。如果原
	 *                                  本目标文件夹中的文件，没在上传包中，会删除该文件）说白了就跟先删
	 *                                  除目标文件夹，在做上传的原理是一样的
	 * @param backupBasePath 备份根路径（如果不为空，会将覆盖前的文件打包）
	 */
	public void undoJar(
			String basePath
			,String jarFilePath
			,boolean isJumpExistFile
			,boolean isDeleteSourceAfterOver
			,boolean nextPath
			,boolean fullUpload
			,String backupBasePath) {
		undoJar(backupBasePath,jarFilePath,isJumpExistFile,isDeleteSourceAfterOver,nextPath,fullUpload,backupBasePath,null);
	}
	
	/**
	 * 处理文件对象中的路径
	 * @param file 文件对象
	 * @return 处理后的路径
	 * 2015年12月2日
	 * @author 马宝刚
	 */
	private String fixFilePath(File file) {
	    if(file==null) {
	        return "";
	    }
	    //构建返回值
	    String rePath = BaseUtil.swapString(file.getPath(),"\\","/");
	    if(rePath.startsWith("/")) {
	        return rePath;
	    }
	    return "/"+rePath;
	}
	
	
	
	/**
	 * 将文件放入jar输出流中
	 * @author 刘虻
	 * 2007-1-6下午12:08:50
	 * @param jos jar输出流
	 * @param filePath 文件路径
	 * @param basePath 压缩根路径，压缩后的jar文件中不包含根路径
	 * @throws Exception 执行发生异常
	 */
	protected void addFileToJar(
			JarOutputStream jos
			,String filePath
			,String basePath) throws Exception {
		
		//导入参数合法化
		if (filePath==null || filePath.length()==0) {
			return;
		}
		//构建要加入的文件对象
		File addFile = new File(filePath);
		//不处理非文件
		if (!addFile.exists() || !addFile.isFile()) {
			return;
		}
		//构造文件输入流
		FileInputStream fin = new FileInputStream(addFile);
		
		//构造jar文件元素
		if (basePath!=null 
				&& basePath.length()>0 
				&& filePath.startsWith(basePath)) {
			filePath = filePath.substring(basePath.length());
		}
		//压缩包中的路径开头不能是/，否则windows自带的解压程序不认压缩包
		if(filePath.startsWith("/")) {
			filePath = filePath.substring(1);
		}
		JarEntry je = new JarEntry(filePath);
		je.setSize(addFile.length()); //放入文件大小信息
		je.setTime(addFile.lastModified()); //放入文件时间
		jos.putNextEntry(je);

		//构造缓存
		byte[] buffer = new byte[bufferSize];
		//构造读入字节
		int readByteSize = 0;
		while ((readByteSize=fin.read(buffer))!=-1) {
			jos.write(buffer, 0, readByteSize);
		} 
		//关闭流
		fin.close();
		//提交
		jos.flush();
		//关闭列表
		jos.closeEntry();
	}
	

	
	/**
	 * 测试入口
	 * @author 刘虻
	 * 2007-1-6下午01:38:04
	 * @param arg0 导入参数
	 */
	public static void main(String[] arg0) {
		
		//构造测试类
		//JarTools jt = new JarTools();
		
		//打包
		//jt.doJarThread("d:/uncom","d:/","d:/test.jar",false,false);
		//解包
		//jt.undoJar("d:/test","d:/test.jar",false,false);
	}
	
	
	
	
}
